﻿#region License Info

//Component of Cronos Package, http://www.codeplex.com/cronos
//Copyright (C) 2009 Anthony Brockwell

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
using ABMath.ModelFramework.Data;
using ICSharpCode.SharpZipLib.GZip;
using MathNet.Numerics.LinearAlgebra;

namespace ABMath.ModelFramework.Visuals
{
    [Serializable]
    public class TSPlotVisual : Visual
    {
        public List<Vector> acfData;

        public string combinedTitle;
        public List<Vector> pacfData;
        public List<TimeSeries> tsData;

        public TSPlotVisual()
        {
        }

        public TSPlotVisual(object initialObject)
        {
            TheObject = initialObject;
        }

        public int[] Lags { get; protected set; }
        public int[] NumObs { get; protected set; }

        /// <summary>
        /// This function constructs a local list of time series and ACF/PACFs.
        /// Lists only contain one element if the input is a univariate time series.
        /// They contain (ts.Dimension) Components if the input is a MVTimeSeries object.
        /// </summary>
        private void BreakIntoComponents()
        {
            int maxLag = 40;

            tsData = new List<TimeSeries>(4);
            acfData = new List<Vector>(4);
            pacfData = new List<Vector>(4);

            var allTS = new List<TimeSeries>(4);

            var tts = TheObject as TimeSeries;
            var mts = TheObject as MVTimeSeries;
            var lts = TheObject as Longitudinal;


            // 1. Create the list of time series to be plotted
            if (tts != null) // then do regular univariate analysis
                if (tts.Count > 0)
                    allTS.Add(tts);
            if (mts != null && mts.Count > 0)
                allTS = mts.ExtractList();
            if (lts != null)
            {
                // get individual components, but line them all up in time
                DateTime baseTime = lts[0].TimeStamp(0);

                for (int i = 0; i < lts.Count; ++i)
                {
                    var ts = new TimeSeries();
                    for (int t = 0; t < lts[i].Count; ++t)
                        ts.Add(baseTime + (lts[i].TimeStamp(t) - lts[i].TimeStamp(0)), lts[i][t], false);
                    ts.Title = lts[i].Title;
                    allTS.Add(ts);
                }
            }

            if (allTS.Count == 0)
                return;

            // 2. Compute the ACF and PACF
            NumObs = new int[allTS.Count];
            for (int i = 0; i < allTS.Count; ++i)
                NumObs[i] = allTS[i].Count;

            var sb = new StringBuilder(50);

            foreach (TimeSeries ts in allTS)
            {
                tsData.Add(ts);
                sb.Append(ts.Title);
            }
            if (lts == null)
                foreach (TimeSeries ts in allTS)
                {
                    Vector acf = ts.ComputeACF(maxLag, true);
                    acfData.Add(acf);
                    Vector pacf = TimeSeries.GetPACFFrom(acf);
                    pacfData.Add(pacf);
                }
            else
                // For longitudinal data, we want the summary: i.e. the average of all the ACFs, but it needs to be based on the overall mean
            {
                Vector overallACF = lts.SampleACF(maxLag);
                Vector overallPACF = TimeSeries.GetPACFFrom(overallACF);
                for (int j = 0; j < overallPACF.Length; ++j)
                    if (Math.Abs(overallPACF[j]) > 1)
                        overallPACF[j] = double.NaN;

                acfData = new List<Vector>();
                pacfData = new List<Vector>();
                for (int i = 0; i < allTS.Count; ++i)
                {
                    acfData.Add(overallACF);
                    pacfData.Add(overallPACF);
                }
            }

            Lags = new int[maxLag + 1];
            for (int i = 0; i <= maxLag; ++i)
                Lags[i] = i;

            combinedTitle = sb.ToString();
        }

        public override string GetDescription()
        {
            return "TS Plot";
        }

        public override string GetShortDescription()
        {
            return "Plot";
        }

        protected override bool OnInputChanged()
        {
            BreakIntoComponents();
            if (TheObject == null)
                return false;

            if (tsData == null || tsData.Count == 0)
                return false;

            if (AssociatedControl != null)
                AssociatedControl.DataHasChanged();

            return true;
        }

        /// <summary>
        /// saves the time series or multivariate time series to a file in .cts (binary compressed format)
        /// </summary>
        public void SaveToFile(string fileName)
        {
            // store it in a binary compressed file
            using (var zos = new GZipOutputStream(File.Create(fileName)))
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(zos, TheObject);
            }    
        }

        /// <summary>
        /// copies the time series to the clipboard (not the plot!)
        /// </summary>
        /// <param name="includeDates"></param>
        /// <param name="includeHeader"></param>
        public void CopyToClipboard(bool includeDates, bool includeHeader)
        {
            var sb = new StringBuilder(16384);

            if (TheObject == null)
                return;

            var tts = TheObject as TimeSeries;
            var mts = TheObject as MVTimeSeries;
            var lts = TheObject as Longitudinal;

            // create header
            if (includeHeader)
            {
                if (includeDates)
                    sb.AppendFormat("Date\t");
                if (mts != null)
                    for (int i = 0; i < mts.Dimension; ++i)
                    {
                        if (i != 0)
                            sb.Append("\t");
                        if (mts.SubTitle[i] != null)
                            sb.AppendFormat(mts.SubTitle[i]);
                    }
                if (tts != null)
                    sb.Append(tts.Title);
                if (lts != null)
                    for (int i = 0; i < lts.MaxCount; ++i)
                    {
                        if (i != 0)
                            sb.AppendFormat("\t");
                        sb.AppendFormat("{0}", (i + 1));
                    }

                sb.AppendLine();
            }

            // now copy data
            if (mts != null)
                for (int t = 0; t < mts.Count; ++t)
                {
                    DateTime timeStamp = mts.TimeStamp(t);
                    if (includeDates)
                        sb.AppendFormat("{0}\t", timeStamp.ToString("MM/dd/yyyy HH:mm:ss.ffff"));
                    for (int i = 0; i < mts.Dimension; ++i)
                    {
                        if (i != 0)
                            sb.Append("\t");
                        sb.AppendFormat("{0:0.000000}", mts[t][i]);
                    }
                    sb.AppendLine();
                }
            if (tts != null)
                for (int t = 0; t < tts.Count; ++t)
                {
                    DateTime timeStamp = tts.TimeStamp(t);
                    if (includeDates)
                        sb.AppendFormat("{0}\t", timeStamp.ToString("MM/dd/yyyy HH:mm:ss.ffff"));
                    sb.AppendFormat("{0:0.000000}", tts[t]);
                    sb.AppendLine();
                }
            if (lts != null)
                for (int i = 0; i < lts.Count; ++i)
                    if (lts[i].Count > 0)
                    {
                        DateTime timeStamp = lts[i].TimeStamp(0);
                        if (includeDates)
                            sb.AppendFormat("{0}\t", timeStamp.ToString("MM/dd/yyyy HH:mm:ss.ffff"));
                        for (int t = 0; t < lts.MaxCount; ++t)
                        {
                            if (t > 0)
                                sb.AppendFormat("\t");
                            if (lts[i].Count > t)
                                sb.AppendFormat("{0:0.000000}", lts[i][t]);
                            else
                                sb.Append("NaN");
                        }
                        sb.AppendLine();
                    }

            Clipboard.SetText(sb.ToString());
        }

        public string BuildTitle(int index)
        {
            string retval;
            var mvts = TheObject as MVTimeSeries;
            string sub = tsData[index].Title;
            if (sub == null)
                sub = "TS#" + (index + 1);
            retval = sub;
            if (mvts != null)
            {
                if (mvts.Title != null)
                    retval = mvts.Title + "(" + sub + ")";
            }
            return retval;
        }

        public override List<Type> GetAllowedInputTypesFor(int socket)
        {
            if (socket != 0)
                throw new SocketException();
            return new List<Type> { typeof(TimeSeries) };
        }

        public override List<Type> GetOutputTypesFor(int socket)
        {
            if (socket != 0)
                throw new SocketException();
            return new List<Type>();
        }
    }
}